#define _FILE_OFFSET_BITS 64
#define _LARGEFILE64_SOURCE

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/fs.h>
#include <stdint.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

/* ------------------------------------------------------------------ */
int is_int(char *str)
/* ------------------------------------------------------------------ */
{
  uint64_t j;
  if (str[0] == 0) return 1;
  for (j=0; j<UINT64_MAX; j++)
  {
    if (str[j] == 0) break;
    if (str[j] < 48 || 57 < str[j])
    {
      return 1;
    }
  }
  return 0;
}

/* ------------------------------------------------------------------ */
uint64_t strtou64(char *str)
/* ------------------------------------------------------------------ */
{
  uint64_t integer = 0;
  uint64_t len, i;
  for (len=1; len<UINT64_MAX; len++)
  {
    if (str[len] == 0) break;
  }
  for (i=0; i<len; i++)
  {
    integer *= 10;
    integer += str[i]-48;
  }
  return integer;
}

/* ------------------------------------------------------------------ */
void show_help()
/* ------------------------------------------------------------------ */
{
  printf(
   "mkstagfs [OPTIONS]\n"
   "Copyright 2011 Tomasz Sobczyk <dottomi@gmail.com>\n"
   "Utility to create SimpleTag File System on a device or as file\n"
   "THIS SOFTWARE COMES WITH NO WARRANTY.\n"
   "IT HAS NOT BEEN TESTED AND MAY DAMAGE YOUR COMPUTER.\n"
   "\n"
   "Options:\n"
   "  -b    Block size (2^value).\n"
   "        Should be equal to target device's block size.\n"
   "        12 (4096) if not specified.\n"
   "  -m    Total size of the file system in bytes.\n"
   "        Required for files. \n"
   "  -i    Maximum number of files (inodes) the file system can store.\n"
   "  -t    Average size for a file's tags in bytes.\n"
   "        The system will allocate <i>*<t> bytes for tag data.\n"
   "  -p    Path to the file or block device to write to.\n"
   "\n"
   "  -f    Flags:\n"
   "        Bit 1: Enable file names.\n"
  );
}

/* ------------------------------------------------------------------ */
int main(unsigned int argc, char **argv)
/* ------------------------------------------------------------------ */
{
  enum { NONE, B, M, I, T, P, F} flag = NONE;
  uint64_t tmpint;

  int       fd = 0;
  uint8_t   flags = 0;
  blksize_t blksize = 0;
  off_t     inodesize = 0;
  uint64_t  files = 0;
  uint64_t  tagsize = 0;
  uint8_t   fsflags = 0;

  unsigned int i;

  if (argc == 1 || (argc & 1) == 0)
  {
    show_help();
    return 0;
  }
  for (i=1; i<argc; i++)
  {
    if (flag == NONE)
    {
      if (argv[i][0] != '-')
      {
        show_help();
        return 1;
      }
      switch (argv[i][1])
      {
      case 'b':
        flag = B;
        break;
      case 'm':
        flag = M;
        break;
      case 'i':
        flag = I;
        break;
      case 't': flag = T; break;
      case 'p': flag = P; break;
      case 'f': flag = F; break;
      default:
        printf("Unknown option '%s' found.\n", &argv[i][1]);
        show_help();
        return 1;
      }
    }
    else
    {
      switch (flag)
      {
        case NONE:
          printf("Error. This shouldn't happen.\n");
          return 1;
        break;
        case B:
          if (is_int(argv[i]) == 1)
          {
            printf("Found a character that is not a digit when processing block size.\n");
            return 1;
          }
          tmpint = strtou64(argv[i]);
          blksize = (1<<tmpint);
        break;
        case M:
          if (is_int(argv[i]) == 1)
          {
            printf("Found a character that is not a digit when processing total size.\n");
            return 1;
          }
          inodesize = strtou64(argv[i]);
        break;
        case I:
          if (is_int(argv[i]) == 1)
          {
            printf("Found a character that is not a digit when processing max files.\n");
            return 1;
          }
          files = strtou64(argv[i]);
        break;
        case T:
          if (is_int(argv[i]) == 1)
          {
            printf("Found a character that is not a digit when processing average tag size.\n");
            return 1;
          }
          tagsize = strtou64(argv[i]);
        break;
        case P:
          fd = open(argv[i], O_CREAT | O_LARGEFILE | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP);
          if (fd == -1)
          {
            printf("Could not open the requested inode for writing.\n");
            perror("open");
            return 1;
          }
          printf("Inode opened for writing: %s\n", argv[i]);
        break;
        case F:
          if (is_int(argv[i]) == 1)
          {
            printf("Found a character that is not a digit when processing flags.\n");
            return 1;
          }
          fsflags = strtou64(argv[i]);
        break;
      }
      flag = NONE;
    }
  }

  if (fd == 0)
  {
    printf("Path not specified.\n");
    return 1;
  }
  struct stat st;
  int ret = fstat(fd, &st);
  if (ret == -1)
  {
    close(fd);
    perror("stat");
    return 1;
  }
  if (!(S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)))
  {
    printf("Requested inode is not a regular file nor a block device - exiting.\n");
    return 1;
  }

  if (blksize == 0)
  {
    printf("Block size not specified.\n");
    if (S_ISREG(st.st_mode))
    {
      blksize = st.st_blksize;
      printf("Got %ld\n", blksize);
    }
    else
    {
      
    }
  }
  if (inodesize == 0 || S_ISBLK(st.st_mode))
  {
    off_t offset = lseek(fd, 0, SEEK_END);
    if (offset == -1)
    {
      close(fd);
      perror("lseek");
      printf("Could not seek to the end of the inode.\n");
      return 1;
    }
    inodesize = offset;
    printf("Got total size: %lld\n", inodesize);
  }
  if (files == 0)
  {
    printf("Maximum number of files not specified.\n");
    printf("Currently there is no algorithm for this. You need to specify it manually.\n");
    return 1;
  }

  printf(
   "Block size: %ld bytes\n"
   "Total size: %llu bytes\n"
   "Max files: %llu\n"
   "timespec size %u bytes\n",
   blksize, inodesize, files, sizeof(struct timespec)
  );
  close(fd);
  return 0;
  
  /*
  for (i=0; i<tsize; i++)
  {
    fputc(0, target);
  }
  fflush(target);
  rewind(target);
  fwrite("STFS", 1, 4, target);
  fwrite(&bsize, 1, 2, target);
  fwrite(&msize, 1, 8, target);
  uint8_t size = sizeof(struct timespec);
  fwrite(&size, 1, 1, target);
  fclose(target);

  return 0;
  */
}
